﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
using Microsoft.VisualStudio.Debugger.Interop;
using UDKDevKitVSX.Concrete;
using UDKDevKitVSXDebugEngine.Definitions;
using UDKDevKitVSXDebugEngine.Exceptions;
using UDKDevKitVSXDebugEngine.Utils;

namespace UDKDevKitVSXDebugEngine
{
    // Represents a logical stack frame on the thread stack. 
    // Also implements the IDebugExpressionContext interface, which allows expression evaluation and watch windows.
    class StackFrame : IDebugStackFrame2, IDebugExpressionContext2
    {
        private readonly Engine engine;
        private readonly Thread thread;

        private string name;
        private bool hasSource;
        private bool current;
 
        public StackFrame(string name, bool current, Engine engine, Thread thread)
        {
            if (engine == null)
            {
                Debug.Fail("ArgumentNullException - engine");
                throw new ArgumentNullException("engine");
            }

            if (thread == null)
            {
                Debug.Fail("ArgumentNullException - thread");
                throw new ArgumentNullException("thread");
            }

            this.engine = engine;
            this.thread = thread;

            this.name = name;

            this.current = current;

            // Try to get source information for this location. If symbols for this file have not been found, this will fail.
            this.hasSource = true;
        }

        public string Name
        {
            get
            {
                if (String.IsNullOrEmpty(this.name))
                {
                    if (this.engine.DebugContext.CallStacks != null)
                    {
                        return this.engine.DebugContext.CallStacks[0];
                    }
                    else
                    {
                        return "N\\A";
                    }
                }

                return this.name;
            }
        }

        #region Non-interface methods

        // Construct a FRAMEINFO for this stack frame with the requested information.
        public void SetFrameInfo(enum_FRAMEINFO_FLAGS dwFieldSpec, out FRAMEINFO frameInfo)
        {
            Debug.WriteLine("Stack Frame: SetFrameInfo");

            frameInfo = new FRAMEINFO();

            // The debugger is asking for the formatted name of the function which is displayed in the callstack window.
            // There are several optional parts to this name including the module, argument types and values, and line numbers.
            // The optional information is requested by setting flags in the dwFieldSpec parameter.
            if ((dwFieldSpec & enum_FRAMEINFO_FLAGS.FIF_FUNCNAME) != 0)
            {
                // If there is source information, construct a string that contains the module name, function name, and optionally argument names and values.
                if (hasSource)
                {
                    frameInfo.m_bstrFuncName = "";

                    if ((dwFieldSpec & enum_FRAMEINFO_FLAGS.FIF_FUNCNAME_MODULE) != 0)
                    {
                        frameInfo.m_bstrFuncName = this.Name;
                    }

                    if ((dwFieldSpec & enum_FRAMEINFO_FLAGS.FIF_FUNCNAME_LINES) != 0)
                    {
                        if (this.current)
                        {
                            frameInfo.m_bstrFuncName += " Line:" + (this.engine.DebugContext.DocumentContext.CurrentDebuggingStatementLine + 1).ToString();
                        }
                    }
                }

                frameInfo.m_dwValidFields |= enum_FRAMEINFO_FLAGS.FIF_FUNCNAME;
            }

            // The debugger is requesting the name of the module for this stack frame.
            if ((dwFieldSpec & enum_FRAMEINFO_FLAGS.FIF_MODULE) != 0)
            {
                frameInfo.m_bstrModule = this.engine.DebugContext.DocumentContext.ClassName;
                frameInfo.m_dwValidFields |=  enum_FRAMEINFO_FLAGS.FIF_MODULE;
            }

            // The debugger is requesting the range of memory addresses for this frame.
            // For the sample engine, this is the contents of the frame pointer.
            if ((dwFieldSpec &  enum_FRAMEINFO_FLAGS.FIF_STACKRANGE) != 0)
            {
                frameInfo.m_addrMin = 0;// m_threadContext.ebp;
                frameInfo.m_addrMax = 0;// m_threadContext.ebp;
                frameInfo.m_dwValidFields |= enum_FRAMEINFO_FLAGS.FIF_STACKRANGE;
            }

            // The debugger is requesting the IDebugStackFrame2 value for this frame info.
            if ((dwFieldSpec & enum_FRAMEINFO_FLAGS.FIF_FRAME) != 0)
            {
                frameInfo.m_pFrame = this;
                frameInfo.m_dwValidFields |= enum_FRAMEINFO_FLAGS.FIF_FRAME;
            }
            
            // Does this stack frame of symbols loaded?
            if ((dwFieldSpec & enum_FRAMEINFO_FLAGS.FIF_DEBUGINFO) != 0)
            {
                frameInfo.m_fHasDebugInfo = hasSource ? 1 : 0;
                frameInfo.m_dwValidFields |= enum_FRAMEINFO_FLAGS.FIF_DEBUGINFO;
            }

            // Is this frame stale?
            if ((dwFieldSpec & enum_FRAMEINFO_FLAGS.FIF_STALECODE) != 0)
            {
                frameInfo.m_fStaleCode = 0;
                frameInfo.m_dwValidFields |= enum_FRAMEINFO_FLAGS.FIF_STALECODE;
            }

            // The debugger would like a pointer to the IDebugModule2 that contains this stack frame.
            if ((dwFieldSpec & enum_FRAMEINFO_FLAGS.FIF_DEBUG_MODULEP) != 0)
            {
                //if (module != null)
                //{
                //    AD7Module ad7Module = (AD7Module)module.Client;
                //    Debug.Assert(ad7Module != null);
                //    frameInfo.m_pModule = ad7Module;
                //    frameInfo.m_dwValidFields |= enum_FRAMEINFO_FLAGS.FIF_DEBUG_MODULEP;
                //}
            }

            if ((dwFieldSpec & enum_FRAMEINFO_FLAGS.FIF_FUNCNAME_LANGUAGE) != 0)
            {
                frameInfo.m_bstrLanguage = "Unreal Script";
                frameInfo.m_dwValidFields |= enum_FRAMEINFO_FLAGS.FIF_FUNCNAME_LANGUAGE;
            }
        }

        // Construct an instance of IEnumDebugPropertyInfo2 for the combined locals and parameters.
        private void CreateProperties(out uint elementsReturned, out IEnumDebugPropertyInfo2 enumObject, bool locals = true, bool globals = true, bool watches = true)
        {
            elementsReturned = 0;

            if (locals && this.engine.DebugContext.Locals != null)
            {
                elementsReturned += (uint) this.engine.DebugContext.Locals.Count;
            }

            if (globals && this.engine.DebugContext.Globals != null)
            {
                elementsReturned += (uint)this.engine.DebugContext.Globals.Count;
            }

            if (watches && this.engine.DebugContext.Watches != null)
            {
                elementsReturned += (uint)this.engine.DebugContext.Watches.Count;
            }

            DEBUG_PROPERTY_INFO[] propInfo = new DEBUG_PROPERTY_INFO[elementsReturned];

            int i = 0;
            if (locals && this.engine.DebugContext.Locals != null)
            {
                foreach (var local in this.engine.DebugContext.Locals)
                {
                    Property property = new Property(local);
                    propInfo[i++] = property.ConstructDebugPropertyInfo(enum_DEBUGPROP_INFO_FLAGS.DEBUGPROP_INFO_STANDARD);
                }
            }

            if (globals && this.engine.DebugContext.Globals != null)
            {
                foreach (var global in this.engine.DebugContext.Globals)
                {
                    Property property = new Property(global, true, false);
                    propInfo[i++] = property.ConstructDebugPropertyInfo(enum_DEBUGPROP_INFO_FLAGS.DEBUGPROP_INFO_STANDARD);
                }
            }

            if (watches && this.engine.DebugContext.Watches != null)
            {
                foreach (var watch in this.engine.DebugContext.Watches)
                {
                    Property property = new Property(watch, false, true);
                    propInfo[i++] = property.ConstructDebugPropertyInfo(enum_DEBUGPROP_INFO_FLAGS.DEBUGPROP_INFO_STANDARD);
                }
            }

            enumObject = new AD7PropertyInfoEnum(propInfo);
        }

        #endregion

        #region IDebugStackFrame2 Members

        // Creates an enumerator for properties associated with the stack frame, such as local variables.
        // The sample engine only supports returning locals and parameters. Other possible values include
        // class fields (this pointer), registers, exceptions...
        int IDebugStackFrame2.EnumProperties(enum_DEBUGPROP_INFO_FLAGS dwFields, uint nRadix, ref Guid guidFilter, uint dwTimeout, out uint elementsReturned, out IEnumDebugPropertyInfo2 enumObject)
        {
            int hr;

            elementsReturned = 0;
            enumObject = null;
            
            try
            {
                if (guidFilter == Guids.guidFilterLocalsPlusArgs 
                    || guidFilter == Guids.guidFilterAllLocalsPlusArgs 
                    || guidFilter == Guids.guidFilterAllLocals)        
                {
                    CreateProperties(out elementsReturned, out enumObject);
                    hr = Constants.S_OK;
                }
                else if (guidFilter == Guids.guidFilterLocals)
                {
                    CreateProperties(out elementsReturned, out enumObject);
                    hr = Constants.S_OK;
                }
                else if (guidFilter == Guids.guidFilterArgs)
                {
                    CreateProperties(out elementsReturned, out enumObject);
                    hr = Constants.S_OK;
                }
                else
                {
                    hr = Constants.E_NOTIMPL;
                }
            }
            catch (ComponentException e)
            {
                return e.HResult;
            }
            catch (Exception e)
            {
                return Core.UnexpectedException(e);
            }
            
            return hr;
        }

        // Gets the code context for this stack frame. The code context represents the current instruction pointer in this stack frame.
        // todo: this function stopping working Autos....
        int IDebugStackFrame2.GetCodeContext(out IDebugCodeContext2 codeContext)
        {
            codeContext = null;

            try
            {
                var memoryAddress = new MemoryAddress(engine, 0 /*m_threadContext.eip*/);
                if (hasSource)
                {
                    memoryAddress.SetDocumentContext(this.engine.DebugContext.DocumentContext);
                    codeContext = memoryAddress;
                    return Constants.S_OK;
                }
                return Constants.S_OK;
            }
            catch (ComponentException e)
            {
                return e.HResult;
            }
            catch (Exception e)
            {
                return Core.UnexpectedException(e);
            }
        }

        // Gets a description of the properties of a stack frame.
        // Calling the IDebugProperty2::EnumChildren method with appropriate filters can retrieve the local variables, method parameters, registers, and "this" 
        // pointer associated with the stack frame. The debugger calls EnumProperties to obtain these values in the sample.
        int IDebugStackFrame2.GetDebugProperty(out IDebugProperty2 property)
        {
            throw new NotImplementedException();
        }

        // Gets the document context for this stack frame. The debugger will call this when the current stack frame is changed
        // and will use it to open the correct source document for this stack frame.
        int IDebugStackFrame2.GetDocumentContext(out IDebugDocumentContext2 docContext)
        {
            docContext = null;
            try
            {
                if (hasSource)
                {
                    docContext = this.engine.DebugContext.DocumentContext;
                    return Constants.S_OK;
                }
            }
            catch (ComponentException e)
            {
                return e.HResult;
            }
            catch (Exception e)
            {
                return Core.UnexpectedException(e);
            }

            return Constants.S_FALSE;
        }

        // Gets an evaluation context for expression evaluation within the current context of a stack frame and thread.
        // Generally, an expression evaluation context can be thought of as a scope for performing expression evaluation. 
        // Call the IDebugExpressionContext2::ParseText method to parse an expression and then call the resulting IDebugExpression2::EvaluateSync 
        // or IDebugExpression2::EvaluateAsync methods to evaluate the parsed expression.
        int IDebugStackFrame2.GetExpressionContext(out IDebugExpressionContext2 ppExprCxt)
        {
            ppExprCxt = (IDebugExpressionContext2)this;
            return Constants.S_OK;
        }

        // Gets a description of the stack frame.
        int IDebugStackFrame2.GetInfo(enum_FRAMEINFO_FLAGS dwFieldSpec, uint nRadix, FRAMEINFO[] pFrameInfo)
        {
            try
            {
                SetFrameInfo(dwFieldSpec, out pFrameInfo[0]);

                return Constants.S_OK;
            }
            catch (ComponentException e)
            {
                return e.HResult;
            }
            catch (Exception e)
            {
                return Core.UnexpectedException(e);
            }
        }

        // Gets the language associated with this stack frame. 
        // In this sample, all the supported stack frames are C++
        int IDebugStackFrame2.GetLanguageInfo(ref string pbstrLanguage, ref Guid pguidLanguage)
        {
            pbstrLanguage = "Unreal Script";
            pguidLanguage = Guids.guidLanguageUnrealScript;
            return Constants.S_OK;
        }

        // Gets the name of the stack frame.
        // The name of a stack frame is typically the name of the method being executed.
        int IDebugStackFrame2.GetName(out string name)
        {
            name = null;

            try
            {
                name = this.Name;

                return Constants.S_OK;
            }
            catch (ComponentException e)
            {
                return e.HResult;
            }
            catch (Exception e)
            {
                return Core.UnexpectedException(e);
            }
        }

        // Gets a machine-dependent representation of the range of physical addresses associated with a stack frame.
        int IDebugStackFrame2.GetPhysicalStackRange(out ulong addrMin, out ulong addrMax)
        {
            addrMin = 0;// m_threadContext.ebp;
            addrMax = 0;// m_threadContext.ebp;

            return Constants.S_OK;
        }

        // Gets the thread associated with a stack frame.
        int IDebugStackFrame2.GetThread(out IDebugThread2 thread)
        {
            thread = this.thread;
            return Constants.S_OK;
        }

        #endregion

        #region IDebugExpressionContext2 Members

        // Retrieves the name of the evaluation context. 
        // The name is the description of this evaluation context. It is typically something that can be parsed by an expression evaluator 
        // that refers to this exact evaluation context. For example, in C++ the name is as follows: 
        // "{ function-name, source-file-name, module-file-name }"
        int IDebugExpressionContext2.GetName(out string pbstrName)
        {
            pbstrName = String.Concat(
                "{ ", 
                this.Name,
                ", ",
                this.engine.DebugContext.DocumentContext.FileName,
                ", ",
                this.engine.DebugContext.DocumentContext.ClassName,
                " }");

            return Constants.S_OK;
        }

        // Parses a text-based expression for evaluation.
        // The engine sample only supports locals and parameters so the only task here is to check the names in those collections.
        int IDebugExpressionContext2.ParseText(string pszCode,
                                                enum_PARSEFLAGS dwFlags, 
                                                uint nRadix, 
                                                out IDebugExpression2 ppExpr, 
                                                out string pbstrError, 
                                                out uint pichError)
        {
            pbstrError = "";
            pichError = 0;
            ppExpr = null;

            Debug.WriteLine(String.Concat("IDebugExpressionContext2.ParseText - ParseText: [", pszCode, "]"));

            try
            {
                ppExpr = new DebuggerExpression(pszCode, this.engine);
                return Constants.S_OK;
            }
            catch (ComponentException e)
            {
                return e.HResult;
            }
            catch (Exception e)
            {
                return Core.UnexpectedException(e);
            }
        }

        #endregion
    }
}

